package core;

import mapper.MapejadorObjectes;
import exception.lwpfException;
import utilities.*;
import java.util.*;
import java.sql.*;
import java.lang.reflect.*;
import javax.sql.DataSource;

/**
 * Created with IntelliJ IDEA.
 * User: Jordi
 * Date: 29/12/12
 * Time: 17:20
 * To change this template use File | Settings | File Templates.
 */
public abstract class ObjectePersistentFctr implements java.io.Serializable, Cloneable{

    public ObjectePersistentFctr() {
        if (Boolean.getBoolean("debug-lwpf") || Boolean.getBoolean("debug-" + getClass().getName())){
            debug = true;
        }
    }
    public ObjectePersistentFctr(boolean synchronizedAccess){
        this.setSincronizacionAccesos(synchronizedAccess);
    }
    public ObjectePersistentFctr(Class requestor, boolean synchronizedAccess){
        this.requestor = requestor;
        this.setSincronizacionAccesos(synchronizedAccess);
    }
    public ObjectePersistentFctr(Class requestor) {
        this.requestor = requestor;
    }
    /*================================== inici =======================*/
    protected boolean debug = false;
    protected MapejadorObjectes mapejadorObjectes = null;
    /* Mapeo de las columnas de la Tabla con los campos de los objetos */
    protected Map mapping = null;
    /* Objeto para hacer persistente */
    protected Class requestor = null;
    /* Identificador de objeto */
    protected int objetoId = 0;
    /* Colección de objetos (data) */
    protected List listaObjetos = new ArrayList();
    /* Colección de objetos para eliminar en la BBDD */
    protected List objetosEliminados = new ArrayList();
    /* Se usa para enviar los objetos no modificados a otra capa */
    protected boolean descartarObjetosNoModificados;
    /* Asigna 'sequence' o deshabilita update/insert/delete */
    protected char[] datosModificacionSequence;
    public static final char DATOS_MODIFICACION_SEQUENCE_DELETE = 'D';
    public static final char DATOS_MODIFICACION_SEQUENCE_INSERT = 'I';
    public static final char DATOS_MODIFICACION_SEQUENCE_UPDATE = 'U';
    /* Deshabilita modificaciones en la BBDD */
    protected boolean soloLectura = false;
    /* Optimistic lock settings ****************************************/
/* Genera procedimientos con WHERE usando solo la clave */
    public static final char KEY_COLUMNS = 'K';
    /* Genera procedimientos con WHERE con la clave y la columnas modificables */
    public static final char KEY_AND_MODIFIED_COLUMNS = 'M';
    /* Genera procedimientos con WHERE con al clave y todas columnas actualizables - este es
    el chequeo de concurrencia más severo */
    public static final char KEY_AND_UPDATEBLE_COLUMNS = 'U';
    /* Current Optimistic lock setting.
    Por defecto es KEY_COLUMNS (Solo Primary Key) */
    protected char optimisticLock = KEY_COLUMNS;
    /*
    Lanza la excepción si PreparedStatement.executeUpdate() no devuelve 1
    para cada ObjectePersistent.
    Esto significa que es muy probable que la fila en la tabla de la BBDD
    fue modificada o eliminada por otro usuario / proceso.
    */
    protected boolean throwOptimisticLockDeleteException = true;
    protected boolean throwOptimisticLockUpdateException = true;
    protected boolean throwFailedInsertException = true;
    /* FIN de Optimistic lock settings *********************************/
/* Apuntador a catalogo en la BBDD */
    protected String catalogo = null;
    /* Si anularNombreCatalogo == true y catalogo en this.mapejadorObjectes es igual a null entonces se usa catalogo como variable */
    protected boolean anularNombreCatalogo = false;
    /* Apuntador a esquema en la BBDD */
    protected String esquema = null;
    /* Apuntador a tabla en la BBDD */
    protected String tabla = null;
    /* Este objeto genera sentencias SQL para modificación de datos */
    protected GuardarInfo guardarInfo = null;
    /* Esta es una utilidad JDBC */
    protected Utilitats utilitats = null;
    /* se usa como argumento en method.invoke(obj, EMPTY_READ_ARG)*/
    protected Object[] EMPTY_READ_ARG = new Object[0];
    /* usar Collections sincronizadas */
    protected boolean sincronizacionAccesos = false;
    /* Si lazyLoding == true entonces solo asignar valores originales vs. original y current */
    protected boolean lazyLoading = false;
    /* Registros por pagina */
    protected int longitudPagina = 0;
    /* No ejecutar INSERTS/UPDATES/DELETES - sólo generar el SQL y los parámetros */
    protected boolean generarSoloSQL = false;
    /* Controlar si cargar() añade objetos a listaObjetos */
    protected boolean adjuntar = false;
    /* Algunas BBDD fallan (como hsqdb 1.7.1) cuando la sentencia UPDATE se usa
    con TABLE_NAME.COLUMN_NAME colocada en la clausula SET. INSERT tambien falla */
    protected boolean noNombreColumnaCompleto = false;
    /* Lanza la excepción o ignora si ObjectePersistent tien menos campos para mapear */
    protected boolean throwMissingFieldException = false;
    /* Se usa para determinar si create/deleted/updated ObjectePersistent pertenece a este objeto factory */
    protected long domainFactoryId = System.currentTimeMillis();
    /* si no es null solo maneja excepciones */
    protected lwpfException lwpfException;
    /* Si es 'false' suprime las excepciones cuando se llama a grabaDatos con listaObjetos vacía */
    protected boolean exceptionListaObjetosVacia = false;
    /* Indica que es un conveniente member/method */
    protected boolean valido = true;
    /* Crea y rellena Metadatos automaticamente */
    protected boolean crearMetadatosDefault = true;
    /* Lee un objeto desde ResultSet como un tipo de datos específico */
    protected ConversorTipus conversorTipus = null;
    /*================================== members end==========================*/
    public int carregar(ResultSet[] rs) throws java.sql.SQLException{
        boolean origAppend = this.isAdjuntar();
        this.setAdjuntar(true);
        int contFiles = 0;
        if (debug) System.out.println("lwpf @@@@@@@@@@@@@@ rs.length: " + rs.length);
        if (debug) System.out.println("lwpf @@@@@@@@@@@@@@ rs: " + Arrays.asList(rs));
        for (int i = 0;i < rs.length;i++){
            if (debug) System.out.println("lwpf @@@@@@@@@@@@@@ resultSet[" + i + "]: " + rs[i]);
            contFiles = contFiles + carregar(rs[i]);
            if (debug) System.out.println("lwpf @@@@@@@@@@@@@@ rowCount_" + i + ": " + contFiles);
        }
        this.setAdjuntar(origAppend);
        return contFiles;
    }
    public int carregar(ResultSet rs) throws java.sql.SQLException{
        long ts1 = -1;
        long ts2 = -1;
        String nomCol = null;
        String nomCamp = null;
        ResultSetMetaData rsmd = rs.getMetaData();
        int rsContColum = rsmd.getColumnCount();
        List rsColums = new ArrayList(rsContColum);
        for (int i = 1;i <= rsContColum;i++){
            rsColums.add(rsmd.getColumnName(i));
        }
        int contColum = 0;
        if (debug) System.out.println("lwpf ============ " + new java.util.Date() + " " + this.getClass() + "::" + this +"::load()::requestor class::" +
                requestor.getName());
        if (!isAdjuntar()){
            this.listaObjetos.clear();
            if (debug) System.out.println("lwpf ============ " + new java.util.Date() + " " + this.getClass() + "::" + this +"::load()::objetoId 1: " +
                    objetoId);
            this.objetoId = 0;
            if (debug) System.out.println("lwpf ============ " + new java.util.Date() + " " + this.getClass() +"::load()::objetoId 2: " + objetoId);
        }
        try{
            if (debug) ts1 = System.currentTimeMillis();
            if (this.mapejadorObjectes == null){
                rellenarMetadatos();
                if (debug) ts2 = System.currentTimeMillis();
                if (debug) System.out.println("lwpf ============ " + new java.util.Date() + " " + this.getClass() + "::" + this +"::rellenarMetadatos: " +
                        (ts2 - ts1) + " ms");
            }
            contColum = this.mapejadorObjectes.getContadorColumna();
            if (debug) System.out.println("lwpf ============ " + new java.util.Date() + " " + this.getClass() + "::" + this +"::load()::columnCount: " +
                    contColum);
            if (conversorTipus == null){
                conversorTipus = new ConversorTipusMySQL(rs);
            }else{
                conversorTipus.setResultSet(rs);
            }
            while (rs.next()){
                MagatzemDades valorsOriginals = null;
                if (!isSoloLectura()){
                    valorsOriginals = new MagatzemDades(contColum);
                }
                Object op = requestor.newInstance();
                if (!isLazyLoading()){
                    ((ObjectePersistent) op).setCarregant(true);
                }
                for (int idxColum = 1; idxColum <= contColum; idxColum++){
                    nomCamp = this.mapejadorObjectes.getNomCamp(idxColum);
                    if (debug) System.out.println("lwpf ============ " + new java.util.Date() + " " + this.getClass() + "::" + this +"::load()::fieldName: " +
                            nomCamp);
                    if (nomCamp != null){ // ResultSet may have more columns then this Object fields, so skip unneeded column
                        nomCol = this.mapejadorObjectes.getNomColumna(idxColum);
                        if (!rsColums.contains(nomCol)){
// ResultSet has less columns then ObjectePersistent fields
                            this.mapejadorObjectes.setGrabable(idxColum, false);
                            continue;
                        }
                        String fieldClassName = this.mapejadorObjectes.getCampoNombreClass(idxColum);
                        Object[] convertedValue = conversorTipus.convertir(fieldClassName, nomCol);
                        if (!isSoloLectura()){
                            valorsOriginals.setNomCampAmbValor(idxColum, nomCamp, convertedValue[0]);
                        }
                        if (!isLazyLoading() || isSoloLectura()){
                            this.mapejadorObjectes.getGrabarMethod(idxColum).invoke(op, convertedValue);
                        }
                    }
                    if (debug) System.out.print("\n");
                }
                if (!isSoloLectura()){
                    ((ObjectePersistent) op).setValorsOriginals(valorsOriginals);
                }
                if (!isLazyLoading()){
                    ((ObjectePersistent) op).setCarregant(false);
                    ((ObjectePersistent) op).setCarregant(true);
                }
                if (isSincronizacionAccesos()){
                    ((ObjectePersistent) op).setObjecteId(this.getSyncNextObjetoId());
                }else{
                    ((ObjectePersistent) op).setObjecteId(this.getNextObjetoId());
                }
                ((ObjectePersistent) op).setEstatPersistencia(ObjectePersistent.ORIGINAL);
                ((ObjectePersistent) op).setPersistentFactoryId(domainFactoryId);
                this.listaObjetos.add(op);
            }
        }catch (InstantiationException ie){
            ie.printStackTrace();
            throw new java.sql.SQLException("Cannot Instantiate: " + requestor);
        }catch (IllegalAccessException iae){
            iae.printStackTrace();
            throw new java.sql.SQLException(iae.getMessage());
        }catch (InvocationTargetException ite){
            ite.printStackTrace();
            throw new java.sql.SQLException(ite.getTargetException().getMessage());
        }catch (Exception e){
            e.printStackTrace();
            throw new java.sql.SQLException(e.getMessage());
        }
        ts2 = System.currentTimeMillis();
        if (debug) System.out.println("lwpf ============ " + new java.util.Date() + " " + this.getClass() + "::" + this +"::load() total: " + (ts2 - ts1 + "                ms"));
        return this.listaObjetos.size();
    }
    /** Leer la propiedad de mapping.
     * @return Value of property mapping.
     *
     */
    public Map getMapping() {
        return mapping;
    }
    /** Asignar nueva propiedad a mapping.
     * @param mapping New obj of property mapping.
     *
     */
    public void setMapping(Map mapping) {
        this.mapping = mapping;
    }
    public void setMapping(String mapping) {
        StringTokenizer tokenizer = new StringTokenizer(mapping, ",", false);
        if (this.mapping == null){
            this.mapping = new Properties();
        }else{
            this.mapping.clear();
        }
        while (tokenizer.hasMoreTokens()){
            String token = tokenizer.nextToken();
            int idx = token.indexOf("=");
            if (idx == -1){
                throw new IllegalArgumentException("ObjectePersistentFctr::setMapping(String mapping): " + mapping + "(Formato Arg.:                        <COLUMN_NAME>=<fieldName>)");
            }
            this.mapping.put(token.substring(0, idx).trim(), token.substring(idx + 1).trim());
        }
    }
    /** Leer la propiedad de requestor.
     * @return Value of property requestor.
     *
     */
    public Class getRequestor() {
        return requestor;
    }
    /** Asignar nueva propiedad a requestor.
     * @param requestor New obj of property requestor.
     *
     */
    public void setRequestor(Class requestor) {
        this.requestor = requestor;
    }
    public List getPrimaryKey(String catalogo, String esquema, String tabla) throws SQLException{
        Object pk = GestorClausPrimaries.getInstance().getPrimaryKey(catalogo, esquema, tabla);
        if (pk == null){
            pk = GestorClausPrimaries.getInstance().getPrimaryKey(this.utilitats.getConnection(), catalogo, esquema, tabla);
        }
        return (List) pk;
    }
    public void setPrimaryKey(String catalogo, String esquema, String tabla, List pk) {
        GestorClausPrimaries.getInstance().setPrimaryKey(catalogo, esquema, tabla, pk);
    }
    public int setObjeto(ObjectePersistent op){
        int idx = -1;
        int id = 0;
        if (op.getPersistentFactoryId() != domainFactoryId){
            op.setPersistentFactoryId(domainFactoryId);
            if (isSincronizacionAccesos()){
                id = this.getSyncNextObjetoId();
                op.setObjecteId(id);
            }else{
                id = this.getNextObjetoId();
                op.setObjecteId(id);
            }
            this.listaObjetos.add(op);
            idx = this.listaObjetos.size() + 1;
        }else{
            id = op.getObjecteId();
            if (id == 0){ // new object
                if (isSincronizacionAccesos()){
                    id = this.getSyncNextObjetoId();
                    op.setObjecteId(id);
                }else{
                    id = this.getNextObjetoId();
                    op.setObjecteId(id);
                }
                this.listaObjetos.add(op);
                idx = this.listaObjetos.size() + 1;
            }else{
                idx = findIdxUsandoObjetoId(id);
                if (idx > 0){
                    this.listaObjetos.set(idx - 1, op);
                }else{
                    this.listaObjetos.add(op);
                }
            }
        }
        return idx;
    }
    /** No puede ser cero el valor del indice idx: idx del primer objeto es 1, del segundo es 2, etc... */
    public void setObjeto(int idx, ObjectePersistent op){
        int id = 0;
        if (op.getPersistentFactoryId() != domainFactoryId){
            op.setPersistentFactoryId(domainFactoryId);
            if (isSincronizacionAccesos()){
                id = this.getSyncNextObjetoId();
                op.setObjecteId(id);
            }else{
                id = this.getNextObjetoId();
                op.setObjecteId(id);
            }
        }else{
            if (op.getObjecteId() == 0){
                if (isSincronizacionAccesos()){
                    id = this.getSyncNextObjetoId();
                    op.setObjecteId(id);
                }else{
                    id = this.getNextObjetoId();
                    op.setObjecteId(id);
                }
            }
        }
        this.listaObjetos.set(idx - 1, op);
    }
    /** No puede ser cero el valor del indice idx: idx del primer objeto es 1, del segundo es 2, etc... */
    public void addObjeto(int idx, ObjectePersistent op){
        int id = 0;
        if (op.getPersistentFactoryId() != domainFactoryId){
            op.setPersistentFactoryId(domainFactoryId);
            if (isSincronizacionAccesos()){
                id = this.getSyncNextObjetoId();
                op.setObjecteId(id);
            }else{
                id = this.getNextObjetoId();
                op.setObjecteId(id);
            }
        }else{
            if (op.getObjecteId() == 0){
                if (isSincronizacionAccesos()){
                    id = this.getSyncNextObjetoId();
                    op.setObjecteId(id);
                }else{
                    id = this.getNextObjetoId();
                    op.setObjecteId(id);
                }
            }
        }
        this.listaObjetos.add(idx - 1, op);
    }
    public int addObjeto(ObjectePersistent op){
        int id = 0;
        if (op.getPersistentFactoryId() != domainFactoryId){
            op.setPersistentFactoryId(domainFactoryId);
            if (isSincronizacionAccesos()){
                id = this.getSyncNextObjetoId();
                op.setObjecteId(id);
            }else{
                id = this.getNextObjetoId();
                op.setObjecteId(id);
            }
        }else{
            if (op.getObjecteId() == 0){
                if (isSincronizacionAccesos()){
                    id = this.getSyncNextObjetoId();
                    op.setObjecteId(id);
                }else{
                    id = this.getNextObjetoId();
                    op.setObjecteId(id);
                }
            }
        }
        this.listaObjetos.add(op);
        return this.listaObjetos.size() - 1;
    }
    public ObjectePersistent findObjetoUsandoObjetoId(int objetoId){
        int idx = findIdxUsandoObjetoId(objetoId);
        return (ObjectePersistent) this.listaObjetos.get(idx - 1);
    }
    /** No puede ser cero el valor del indice idx: idx del primer objeto es 1, del segundo es 2, etc... */
    public int findIdxUsandoObjetoId(int objetoId){
        int id = 0;
        int idx = 0;
        Iterator it = this.listaObjetos.iterator();
        while (it.hasNext()){
            idx ++;
            ObjectePersistent op = (ObjectePersistent) it.next();
            id = op.getObjecteId();
            if (objetoId == id){
                break;
            }
        }
        return idx;
    }
    protected synchronized int getSyncNextObjetoId() {
        this.objetoId ++;
        return this.objetoId;
    }
    protected int getNextObjetoId() {
        this.objetoId ++;
        return this.objetoId;
    }
    /** Leer la propiedad de listaObjetos.
     * @return Value of property listaObjetos.
     *
     */
    public List getListaObjetos() {
        return listaObjetos;
    }
    /** Asignar nueva propiedad a listaObjetos.
     * @param listaObjetos New obj of property listaObjetos.
     *
     */
    public void setListaObjetos(List listaObjetos) {
        this.listaObjetos = listaObjetos;
    }
    /* No se elimina de la BBDD solo de listaObjetos */
    public boolean descartar(ObjectePersistent op) {
        int idx = findIdxUsandoObjetoId(op.getObjecteId());
        if (idx > 0){
            this.listaObjetos.remove(idx - 1);
            return true;
        }
        return false;
    }
    public boolean eliminar(ObjectePersistent op){
        boolean completado = false;
        int removedIdx = -1;
        if (op.getPersistentFactoryId() == domainFactoryId){
            int idx = findIdxUsandoObjetoId(op.getObjecteId());
            if (!antesEliminar(op)){
                return completado;
            }
            if (idx > 0){
                if (op.getEstatPersistencia() == ObjectePersistent.CREAT){
                    this.listaObjetos.remove(idx - 1); // No se elimina de la BBDD solo de listaObjetos
                    completado = false;
                }else{
                    if (op.esborrar()){
                        this.objetosEliminados.add(op);
                        removedIdx = this.objetosEliminados.size() + 1;
                        this.listaObjetos.remove(idx - 1);
                        completado = true;
                    }else{ // descartar?
//this.listaObjetos.eliminar(idx - 1);
                        completado = false;
                    }
                }
            }else{
                if (isSincronizacionAccesos()){
                    op.setObjecteId(this.getSyncNextObjetoId());
                }else{
                    op.setObjecteId(this.getNextObjetoId());
                }
                if (op.getEstatPersistencia() == ObjectePersistent.CREAT){
// No se elimina de la BBDD solo de listaObjetos
                    completado = false;
                }else{
                    if (op.esborrar()){
                        this.objetosEliminados.add(op);
                        completado = true;
                    }
                }
            }
            if (!despuesEliminar(removedIdx)){
                return false;
            }
        }else{
            op.setPersistentFactoryId(domainFactoryId);
            if (!antesEliminar(op)){
                return false;
            }
            if (isSincronizacionAccesos()){
                op.setObjecteId(this.getSyncNextObjetoId());
            }else{
                op.setObjecteId(this.getNextObjetoId());
            }
            if (op.getEstatPersistencia() == ObjectePersistent.CREAT){
                this.objetosEliminados.add(op); // No se elimina de la BBDD solo de listaObjetos
                completado = false;
            }else{
                if (op.esborrar()){
                    this.objetosEliminados.add(op);
                    completado = true;
                }
            }
            if (!despuesEliminar(removedIdx)){
                return false;
            }
        }
        return completado;
    }
    // es necesario contar con este paso
    public boolean antesEliminar(ObjectePersistent op) {
        return true;
    }
    /** No puede ser cero el valor del indice idx: idx del primer objeto es 1, del segundo es 2, etc... es necesario contar con este paso */
    public boolean despuesEliminar(int idx){
        return true;
    }
    public boolean almacenar(ObjectePersistent op) {
        boolean completado = false;
        int idx = -1;
        if (op.getPersistentFactoryId() == domainFactoryId){ // op podía ser de otro ObjectePersistentFctr o creado a mano o de otra manera
            idx = findIdxUsandoObjetoId(op.getObjecteId());
            if (!antesAlmacenar(op)){
                return false;
            }
            if (idx > 0){
                if (op.guardar()){
                    this.listaObjetos.set(idx - 1, op);
                    completado = true;
                }
            }else{
                if (isSincronizacionAccesos()){
                    op.setObjecteId(this.getSyncNextObjetoId());
                }else{
                    op.setObjecteId(this.getNextObjetoId());
                }
                if (op.guardar()){
                    this.listaObjetos.add(op);
                    idx = this.listaObjetos.size() - 1;
                    completado = true;
                }else{
                    completado = false;
                }
            }
            if (!despuesAlmacenar(idx)){
                return false;
            }
        }else{
            op.setPersistentFactoryId(domainFactoryId);
            if (isSincronizacionAccesos()){
                op.setObjecteId(this.getSyncNextObjetoId());
            }else{
                op.setObjecteId(this.getNextObjetoId());
            }
            if (!antesAlmacenar(op)){
                return false;
            }
            if (op.guardar()){
                this.listaObjetos.add(op);
                idx = this.listaObjetos.size() - 1;
                completado = true;
            }else{
                completado = false;
            }
            if (!despuesAlmacenar(idx)){
                return false;
            }
        }
        return completado;
    }
    // es necesario contar con este paso
    public boolean antesAlmacenar(ObjectePersistent domainObjectx){
        return true;
    }
    /** No puede ser cero el valor del indice idx: idx del primer objeto es 1, del segundo es 2, etc... es necesario contar con este paso */
    public boolean despuesAlmacenar(int idx){
        return true;
    }
    public boolean crear(ObjectePersistent op) {
        if (op.getPersistentFactoryId() != domainFactoryId){
            op.setPersistentFactoryId(domainFactoryId);
        }
        int idx = -1;
        boolean completado = false;
        if (!antesCrear(op)){
            return false;
        }
        if (op.crear()){
            idx = this.setObjeto(op);
            completado = true;
        }
        if (!despuesCrear(idx)){
            return false;
        }
        return completado;
    }
    // es necesario contar con este paso
    public boolean antesCrear(ObjectePersistent domainObject){
        return true;
    }
    /** No puede ser cero el valor del indice idx: idx del primer objeto es 1, del segundo es 2, etc... es necesario contar con este paso */
    public boolean despuesCrear(int idx){
        return true;
    }
    /** Leer la propiedad de objetosEliminados.
     * @return Value of property objetosEliminados.
     *
     */
    public List getObjetosEliminados() {
        return objetosEliminados;
    }
    /** Asignar nueva propiedad a objetosEliminados.
     * @param objetosEliminados New obj of property objetosEliminados.
     *
     */
    public void setObjetosEliminados(List objetosEliminados) {
        this.objetosEliminados = objetosEliminados;
    }
    /** Indexed Leer la propiedad de datosModificacionSequence.
     * @param idx Index of the property.
     * @return Value of the property at <CODE>idx</CODE>.
     *
     */
    public char getDatosModificacionSequence(int idx) {
        return this.datosModificacionSequence[idx];
    }
    /** Leer la propiedad de datosModificacionSequence.
     * @return Value of property datosModificacionSequence.
     *
     */
    public char[] getDatosModificacionSequence() {
        return this.datosModificacionSequence;
    }
    /** Asignar nueva propiedad a datosModificacionSequence.
     * @param datosModificacionSequence New obj of property datosModificacionSequence.
     *
     */
    public void setDatosModificacionSequence(char[] datosModificacionSequence) {
        this.datosModificacionSequence = datosModificacionSequence;
    }
    /** Leer la propiedad de descartarObjetosNoModificados.
     * @return Value of property descartarObjetosNoModificados.
     *
     */
    public boolean isDescartarObjetosNoModificados() {
        return descartarObjetosNoModificados;
    }
    /** Asignar nueva propiedad a descartarObjetosNoModificados.
     * @param descartarObjetosNoModificados New obj of property descartarObjetosNoModificados.
     *
     */
    public void setDescartarObjetosNoModificados(boolean descartarObjetosNoModificados) {
        ListIterator li = this.listaObjetos.listIterator();
        while (li.hasNext()){
            ObjectePersistent op = (ObjectePersistent) li.next();
            if (op.getEstatPersistencia() == op.ORIGINAL){
                li.remove();
            }
        }
        this.descartarObjetosNoModificados = descartarObjetosNoModificados;
    }
    /** Leer la propiedad de optimisticLock.
     * @return Value of property optimisticLock.
     *
     */
    public char getOptimisticLock() {
        return optimisticLock;
    }
    /** Asignar nueva propiedad a optimisticLock.
     * @param optimisticLock New obj of property optimisticLock.
     *
     */
    public void setOptimisticLock(char optimisticLock) {
        this.optimisticLock = optimisticLock;
    }
    /** Leer la propiedad de soloLectura.
     * @return Value of property soloLectura.
     *
     */
    public boolean isSoloLectura() {
        return soloLectura;
    }
    /** Asignar nueva propiedad a soloLectura.
     * @param soloLectura New obj of property soloLectura.
     * makes this non-updateable.
     */
    public void setSoloLectura(boolean soloLectura) {
        this.soloLectura = soloLectura;
    }
    public void setOriginal() {
        if (isSoloLectura()){
            throw new IllegalStateException("No se puede usar setOriginal() para objetos readOnly");
        }
        listaObjetos.add(this.objetosEliminados);
        ListIterator li = listaObjetos.listIterator();
        while (li.hasNext()){
            ObjectePersistent op = (ObjectePersistent) li.next();
            if (op.getEstatPersistencia() == ObjectePersistent.ESBORRAT &&
                    op.getValorsOriginals() != null &&
                    op.getValorsOriginals().getComptadorCamps() > 0){
                op.setEstatPersistencia(ObjectePersistent.ORIGINAL);
            }else if (op.getEstatPersistencia() == ObjectePersistent.CREAT &&
                    op.getValorsOriginals() == null ||
                    op.getValorsOriginals().getComptadorCamps() < 1){
                li.remove();
            }else if (op.getEstatPersistencia() == ObjectePersistent.GUARDAT &&
                    op.getValorsOriginals() != null &&
                    op.getValorsOriginals().getComptadorCamps() > 0){
                op.setEstatPersistencia(ObjectePersistent.ORIGINAL);
            }else{
                throw new IllegalArgumentException("No valido estado de SQL");
            }
        }
        objetosEliminados.clear();
    }
    /** Leer la propiedad de esquema.
     * @return Value of property esquema.
     *
     */
    public String getEsquema() {
        return esquema;
    }
    /** Asignar nueva propiedad a esquema.
     * @param esquema New obj of property esquema.
     *
     */
    public void setEsquema(String esquema) {
        this.esquema = esquema;
    }
    /** Leer la propiedad de tabla.
     * @return Value of property tabla.
     *
     */
    public String getTabla() {
        return tabla;
    }
    /** Asignar nueva propiedad a tabla.
     * @param tabla New obj of property tabla.
     *
     */
    public void setTabla(String tabla) {
        this.tabla = tabla;
    }
    public boolean grabarDatos(){
        if (!isValido()){
            return false;
        }
        if (!antesGrabarDatos()){
            return false;
        }
        if (this.guardarInfo == null){
            this.guardarInfo = new GuardarInformacio();
        }
        this.guardarInfo.setExcepcionEnObjectListVacios(exceptionListaObjetosVacia);
        boolean completado = this.guardarInfo.guardarInfo(this);
        if (!despuesGrabarDatos()){
            return false;
        }
        return completado;
    }
    // es necesario contar con este paso
    public boolean antesGrabarDatos(){
        return true;
    }
    // es necesario contar con este paso
    public boolean despuesGrabarDatos(){
        return true;
    }
    /** Leer la propiedad de mapejadorObjectes.
     * @return Value of property mapejadorObjectes.
     *
     */
    public MapejadorObjectes getMetadatos() {
        if (crearMetadatosDefault && this.mapejadorObjectes == null){
            rellenarMetadatos();
        }
        return this.mapejadorObjectes;
    }
    public void rellenarMetadatos() {
        try{
            this.mapejadorObjectes = new MapejadorObjectes();
            this.mapejadorObjectes.setThrowExcepcionCampoAusente(throwMissingFieldException);
            this.mapejadorObjectes.llenar(getMapping(), this.requestor);
            Collection tablas = (this.mapejadorObjectes.getTablasMap()).values();
            if (debug) System.out.println("lwpf ============ " + new java.util.Date() + " " + this.getClass() + "::" + this +"::mapejadorObjectes.getTables(): " +
                    this.mapejadorObjectes.getTablasMap());
            if (!this.isSoloLectura()){ // no es necesario PK si es tipo ReadOnly
/* Pide todos los nombre de tabla distintos por solicitud (ObjectePersistent) */
                Iterator iter = tablas.iterator();
                while (iter.hasNext()){
                    String[] tablaId = (String[]) iter.next();
                    if (tablaId[0] == null && anularNombreCatalogo){
                        tablaId[0] = this.catalogo;
                    }
                    List pk = getPrimaryKey(tablaId[0], tablaId[1], tablaId[2]);
                    if (debug) System.out.println("lwpf ============ " + new java.util.Date() + " " + this.getClass() + "::" + this +"::pk: " + pk);
                }
            }
            if (tablas.size() == 1){
//hay una sola tabla para este objeto, de otra manera se debe asignar catalogo y/o esquema y tabla para update/delete/insert cada tabla
                if (this.mapejadorObjectes.getNomCataleg(1) == null && !anularNombreCatalogo){
                    setCatalogo(this.catalogo);
                }
                setEsquema(this.mapejadorObjectes.getNomEsquema(1));
                setTabla(this.mapejadorObjectes.getNomTaula(1));
                if (debug) System.out.println("lwpf ============ " + new java.util.Date() + " " + this.getClass() + "::" + this +"::this.catalogo: " +
                        this.catalogo);
                if (debug) System.out.println("lwpf ============ " + new java.util.Date() + " " + this.getClass() + "::" + this +"::esquema: " +
                        getEsquema());
                if (debug) System.out.println("lwpf ============ " + new java.util.Date() + " " + this.getClass() + "::" + this +"::tablas: " + getTabla());
            }
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    /** Asignar nueva propiedad a mapejadorObjectes.
     * @param mapejadorObjectes New obj of property mapejadorObjectes.
     *
     */
    public void setMetadatos(MapejadorObjectes mapejadorObjectes) {
        this.mapejadorObjectes = mapejadorObjectes;
    }
    /** Leer la propiedad de utilitats.
     * @return Value of property utilitats.
     *
     */
    public Utilitats getUtilitats() {
        if (this.utilitats == null){
            this.utilitats = new Utilitats();
        }
        return this.utilitats;
    }
    /** Asignar nueva propiedad a utilitats.
     * @param utilitats New obj of property utilitats.
     *
     */
    public void setUtilitats(Utilitats utilitats) {
        this.utilitats = utilitats;
    }
    /** Leer la propiedad de throwOptimisticLockDeleteException.
     * @return Value of property throwOptimisticLockDeleteException.
     *
     */
    public boolean isThrowOptimisticLockDeleteException() {
        return throwOptimisticLockDeleteException;
    }
    /** Asignar nueva propiedad a throwOptimisticLockDeleteException.
     * @param throwOptimisticLockDeleteException New obj of property throwOptimisticLockDeleteException.
     *
     */
    public void setThrowOptimisticLockDeleteException(boolean throwOptimisticLockDeleteException) {
        this.throwOptimisticLockDeleteException = throwOptimisticLockDeleteException;
    }
    /** Leer la propiedad de throwOptimisticLockUpdateException.
     * @return Value of property throwOptimisticLockUpdateException.
     *
     */
    public boolean isThrowOptimisticLockUpdateException() {
        return throwOptimisticLockUpdateException;
    }
    /** Asignar nueva propiedad a throwOptimisticLockUpdateException.
     * @param throwOptimisticLockUpdateException New obj of property throwOptimisticLockUpdateException.
     *
     */
    public void setThrowOptimisticLockUpdateException(boolean throwOptimisticLockUpdateException) {
        this.throwOptimisticLockUpdateException = throwOptimisticLockUpdateException;
    }
    public void setThrowFailedInsertException(boolean throwFailedInsertException){
        this.throwFailedInsertException = throwFailedInsertException;
    }
    public boolean isThrowFailedInsertException(){
        return this.throwFailedInsertException;
    }
    /**
     * MUY IMPORTANTE! *************************************************************
     * Se debe llamar a este método tras una llamada COMMIT satisfactorio a fin de
     * conseguir sincronizar los valores originales y los actuales: sobreescribe los
     * valores actuales a los originales y cambia el estado de la variable
     * persistentState == ORIGINAL y descarta los objetos borrados de la BBDD
     */
    public void sincronizarEstadoPersistencia(){
        if (isSoloLectura()){
            throw new IllegalStateException("Cannot call sincronizarEstadoPersistencia() for readOnly object");
        }
        try{
            objetosEliminados.clear();
            ListIterator li = listaObjetos.listIterator();
            while (li.hasNext()){
                boolean copiarValores = true;
                boolean creado = false;
                ObjectePersistent op = (ObjectePersistent) li.next();
                if (debug) System.out.println("lwpf ============ " + new java.util.Date() + " " + this.getClass() + "::" + this
                        +"::syncSqlStatus()::ObjectePersistent: " + op + " \n");
                if (op.getEstatPersistencia() == ObjectePersistent.ESBORRAT &&
                        op.getValorsOriginals() != null &&
                        op.getValorsOriginals().getComptadorCamps() > 0){
                    li.remove();
                    copiarValores = false;
                }else if (op.getEstatPersistencia() == ObjectePersistent.CREAT &&
                        (op.getValorsOriginals() == null ||
                                op.getValorsOriginals().getComptadorCamps() < 1)){
                    op.setEstatPersistencia(ObjectePersistent.ORIGINAL);
                    creado = true;
                }else if (op.getEstatPersistencia() == ObjectePersistent.GUARDAT &&
                        op.getValorsOriginals() != null &&
                        op.getValorsOriginals().getComptadorCamps() > 0){
                    op.setEstatPersistencia(ObjectePersistent.ORIGINAL);
                }else if (op.getEstatPersistencia() == ObjectePersistent.ORIGINAL){
// no hacer nada
                    continue;
                }else{
                    throw new IllegalArgumentException("Invalid PersistentState");
                }
                if (copiarValores){ // Sincroniza values en el Objeto y sus valoresOriginales
                    MagatzemDades valorsOriginals = null;
                    if (creado){
                        valorsOriginals = new MagatzemDades(mapping.size());
                    }else{
                        valorsOriginals = op.getValorsOriginals();
                    }
                    Iterator iter = mapping.values().iterator();
                    int idxCampo = 0;
                    if (debug) System.out.println("lwpf ============ " + new java.util.Date() + " " + this.getClass() + "::" + this +"::syncSqlStatus()::creado:                            " + creado + " \n");
                    while(iter.hasNext()){
                        idxCampo ++;
                        String nombreCampo = (String) iter.next();
                        if (creado){
                            Method leeMethod = this.mapejadorObjectes.getLeerMethod(this.mapejadorObjectes.getIndiceColumnaPorNombreCampo(nombreCampo));
                            Object obj = leeMethod.invoke(op, EMPTY_READ_ARG);
                            valorsOriginals.setNomCampAmbValor(idxCampo, nombreCampo, obj);
                        }else{
                            if (valorsOriginals.findIndexCamp(nombreCampo) != -1){
                                Method leeMethod = this.mapejadorObjectes.getLeerMethod(this.mapejadorObjectes.getIndiceColumnaPorNombreCampo(nombreCampo));
                                Object obj = leeMethod.invoke(op, EMPTY_READ_ARG);
                                op.setValorOriginal(nombreCampo, obj);
                            }
                        }
                    }
                    if (creado){
                        op.setValorsOriginals(valorsOriginals);
                        creado = false;
                    }
                    op.setModificat(false);
                }
            }
            if (this.guardarInfo != null){
                this.guardarInfo.reset();
            }
            valido = true;
        }catch (Throwable t){
            if (t instanceof InvocationTargetException){
                throw new RuntimeException(((InvocationTargetException) t).getTargetException());
            }else{
                throw new RuntimeException(t);
            }
        }
    }
    /** Leer la propiedad de removedCount.
     * @return Value of property removedCount.
     *
     */
    public int getContadorBajas() {
        if (guardarInfo == null){
            throw new NullPointerException("DataWriter no inicializado");
        }
        return guardarInfo.getContadorBajas();
    }
    /** Leer la propiedad de createdCount.
     * @return Value of property createdCount.
     *
     */
    public int getContadorAltas() {
        if (guardarInfo == null){
            throw new NullPointerException("DataWriter no inicializado");
        }
        return guardarInfo.getContadorAltas();
    }
    /** Leer la propiedad de modifiedCount.
     * @return Value of property modifiedCount.
     *
     */
    public int getContadorModificaciones() {
        if (guardarInfo == null){
            throw new NullPointerException("DataWriter no inicializado");
        }
        return guardarInfo.getContadorModificaciones();
    }
    /** Leer la propiedad de grabarDatos.
     * @return Value of property grabarDatos.
     *
     */
    public GuardarInfo getGrabarDatos() {
        if (guardarInfo == null){
            guardarInfo = new GuardarInformacio();
        }
        return guardarInfo;
    }
    /** Asignar nueva propiedad a guardarInfo.
     * @param guardarInfo New obj of property guardarInfo.
     *
     */
    public void setGrabarDatos(GuardarInfo guardarInfo) {
        this.guardarInfo = guardarInfo;
    }
    /** Leer la propiedad de sincronizacionAccesos.
     * @return Value of property sincronizacionAccesos.
     *
     */
    public boolean isSincronizacionAccesos() {
        return sincronizacionAccesos;
    }
    /** Asignar nueva propiedad a sincronizacionAccesos.
     * @param sincronizacionAccesos New obj of property sincronizacionAccesos.
     *
     */
    public void setSincronizacionAccesos(boolean sincronizacionAccesos) {
        if (sincronizacionAccesos){
            this.objetosEliminados = Collections.synchronizedList(objetosEliminados);
            this.listaObjetos = Collections.synchronizedList(listaObjetos);
            this.sincronizacionAccesos = sincronizacionAccesos;
        }
    }
    /** Leer la propiedad de lazyLoading.
     * @return Value of property lazyLoading.
     *
     */
    public boolean isLazyLoading() {
        return lazyLoading;
    }
    /** Asignar nueva propiedad a lazyLoading.
     * @param lazyLoading New obj of property lazyLoading.
     *
     */
    public void setLazyLoading(boolean lazyLoading) {
        this.lazyLoading = lazyLoading;
    }
    /** Leer la propiedad de longitudPagina.
     * @return Value of property longitudPagina.
     *
     */
    public int getLongitudPagina() {
        return longitudPagina;
    }
    /** Asignar nueva propiedad a longitudPagina.
     * @param longitudPagina New obj of property longitudPagina.
     *
     */
    public void setLongitudPagina(int longitudPagina) {
        if (longitudPagina < 0){
            throw new IllegalArgumentException("Longitud pagina no valida: " + longitudPagina);
        }
        this.longitudPagina = longitudPagina;
    }
    /** No puede ser cero el valor del indice idx: idx del primer objeto es 1, del segundo es 2, etc... */
    public Cursor getPagina(int numeroPagina) {
        Cursor mp = new Cursor();
        mp.setObjetosTotales(this.listaObjetos.size());
        if (getLongitudPagina() == 0){
            mp.setNumeroPagina(1);
            mp.setTotalPaginas(1);
            mp.setPagina(this.listaObjetos);
        }else{
            List pagina = new ArrayList();
            if (this.listaObjetos.isEmpty()){
                mp.setNumeroPagina(0);
                mp.setTotalPaginas(0);
                mp.setPagina(pagina);
            }else{
                double d1 = new Integer(this.listaObjetos.size()).doubleValue();
                double d2 = new Integer(this.longitudPagina).doubleValue();
                double d3 = Math.ceil(d1 / d2);
                int totalPaginas = (new Double(d3)).intValue();
                mp.setTotalPaginas(totalPaginas);
                if (numeroPagina > totalPaginas){
                    numeroPagina = totalPaginas;
                }else if (numeroPagina < 1){
                    numeroPagina = 1;
                }else{
//
                }
                mp.setNumeroPagina(numeroPagina);
                ListIterator li = this.listaObjetos.listIterator((numeroPagina - 1) * getLongitudPagina());
                int cont = 0;
                while (li.hasNext() && cont < getLongitudPagina()){
                    Object obj = li.next();
                    pagina.add(obj);
                    cont ++;
                }
                mp.setPagina(pagina);
            }
        }
        return mp;
    }
    /** Leer la propiedad de generarSoloSQL.
     * @return Value of property generarSoloSQL.
     *
     */
    public boolean isGenerarSoloSQL() {
        return generarSoloSQL;
    }
    /** Asignar nueva propiedad a generarSoloSQL.
     * @param generarSoloSQL New obj of property generarSoloSQL.
     *
     */
    public void setGenerarSoloSQL(boolean generarSoloSQL) {
        this.generarSoloSQL = generarSoloSQL;
    }
    public List getSQLGeneradas() {
        if (guardarInfo == null){
            throw new NullPointerException("DataWriter no inicializado");
        }
        return this.guardarInfo.getSQLGeneradas();
    }
    public String getSQLcomoXMLGeneradas() {
        if (guardarInfo == null){
            throw new NullPointerException("DataWriter no inicializado");
        }
        return this.guardarInfo.getGenerarSentenciasSQLcomoXML();
    }
    /** Leer la propiedad de adjuntar.
     * @return Value of property adjuntar.
     *
     */
    public boolean isAdjuntar() {
        return adjuntar;
    }
    /** Asignar nueva propiedad a adjuntar.
     * @param adjuntar New obj of property adjuntar.
     *
     */
    public void setAdjuntar(boolean adjuntar) {
        this.adjuntar = adjuntar;
    }
    public Throwable getErroresDetectados() {
        if (this.guardarInfo == null){
            return null;
        }
        return this.guardarInfo.getErroresDetectados();
    }
    /** Leer la propiedad de catalogo.
     * @return Value of property catalogo.
     *
     */
    public String getCatalogo() {
        return catalogo;
    }
    /** Asignar nueva propiedad a catalogo.
     * @param catalogo New obj of property catalogo.
     *
     */
    public void setCatalogo(String catalogo) {
        this.catalogo = catalogo;
    }
    /** Leer la propiedad de noNombreColumnaCompleto.
     * @return Value of property noNombreColumnaCompleto.
     *
     */
    public boolean isNoNombreColumnaCompleto() {
        return noNombreColumnaCompleto;
    }
    /** Asignar nueva propiedad a noNombreColumnaCompleto.
     * @param noNombreColumnaCompleto New obj of property noNombreColumnaCompleto.
     *
     */
    public void setNoNombreColumnaCompleto(boolean noNombreColumnaCompleto) {
        this.noNombreColumnaCompleto = noNombreColumnaCompleto;
    }
    /**
     * Leer la propiedad de debug.
     * @return Value of property debug.
     */
    public boolean isDebug() {
        return debug;
    }
    public void limpiarDatos(){
        if (this.listaObjetos != null) this.listaObjetos.clear();
        if (this.objetosEliminados != null) this.objetosEliminados.clear();
        valido = true;
    }
    /**
     * Asignar nueva propiedad a debug.
     * @param debug New obj of property debug.
     */
    public void setDebug(boolean debug) {
        this.debug = debug;
    }
    public boolean isAnularNombreCatalogo() {
        return anularNombreCatalogo;
    }
    public void setAnularNombreCatalogo(boolean anularNombreCatalogo) {
        this.anularNombreCatalogo = anularNombreCatalogo;
    }
    public void setManejadorExcepciones(lwpfException exceptionHandle){
        this.lwpfException = exceptionHandle;
        lwpfException.setObjectePersistentFctr(this);
    }
    public lwpfException getManejadorExcepciones(){
        return this.lwpfException;
    }
    // es necesario contar con este paso
    public boolean isValido() {
        return valido;
    }
    // crear ContenedorDatos vacío desde Metadatos
    public MagatzemDades crearValoresOriginales() throws SQLException{
        MagatzemDades cd = new MagatzemDades(this.mapejadorObjectes.getContadorColumna());
        for (int i = 1;i <= this.mapejadorObjectes.getContadorColumna();i++){
            cd.setNomCamp(i, this.mapejadorObjectes.getNomCamp(i));
        }
        return cd;
    }
    public void setDataSource(DataSource dataSource){
        getUtilitats().setDataSource(dataSource);
    }
    public DataSource getDataSource() throws SQLException{
        return getUtilitats().getDataSource();
    }
    public void setDataSourceName(String dataSourceName){
        getUtilitats().setNombreOrigenDatos(dataSourceName);
    }
    public boolean isExceptionListaObjetosVacia() {
        return exceptionListaObjetosVacia;
    }
    public void setExceptionListaObjetosVacia(boolean exceptionListaObjetosVacia) {
        this.exceptionListaObjetosVacia = exceptionListaObjetosVacia;
    }
    public boolean isCrearMetadatosDefault() {
        return crearMetadatosDefault;
    }
    public void setCrearMetadatosDefault(boolean crearMetadatosDefault) {
        this.crearMetadatosDefault = crearMetadatosDefault;
    }
    public boolean isThrowMissingFieldException() {
        return throwMissingFieldException;
    }
    public void setThrowMissingFieldException(boolean throwMissingFieldException) {
        this.throwMissingFieldException = throwMissingFieldException;
    }
    public ConversorTipus getConversorTipus() {
        return conversorTipus;
    }
    public void setConversorTipus(ConversorTipus conversorTipus) {
        this.conversorTipus = conversorTipus;
    }
    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        Object obj = null;
        Method[] methods = getClass().getMethods();
        for (int i = 0;i < methods.length;i++){
            String nombreMethod = methods[i].getName();
            if (nombreMethod.equals("getMetadatos") || nombreMethod.equals("getClass")){
                continue;
            }
            if(nombreMethod.equals("getDatosModificacionSequence") && methods[i].getParameterTypes().length == 0){
                if (datosModificacionSequence != null && datosModificacionSequence.length > 0){
                    sb.append("datosModificacionSequence=").append(new String(datosModificacionSequence)).append("&");
                }else{
                    sb.append("datosModificacionSequence=").append("&");
                }
            }else if ((nombreMethod.startsWith("get") || nombreMethod.startsWith("is")) && methods[i].getParameterTypes().length == 0){
                try{
                    obj = methods[i].invoke(this, EMPTY_READ_ARG);
                }catch (Throwable t){
                    continue;
                }
                String inicialCharCampo = "";
                if (nombreMethod.startsWith("is")){
                    inicialCharCampo = nombreMethod.substring(2, 3).toLowerCase();
                    sb.append(inicialCharCampo);
                    sb.append(nombreMethod.substring(3));
                }else if (nombreMethod.startsWith("get")){
                    inicialCharCampo = nombreMethod.substring(3, 4).toLowerCase();
                    sb.append(inicialCharCampo);
                    sb.append(nombreMethod.substring(4));
                }
                sb.append("=");
                sb.append((obj == null)?"":obj);
                sb.append("&");
            }
        }
        return sb.toString();
    }

}
